package semantic_matching.entity_search.entity_tree;

import com.ambiverse.api.model.Entity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import query_understanding.query_language.doc_entity_tree.EntityQueryTree;
import query_understanding.query_language.doc_entity_tree.QueryEdge;
import query_understanding.query_language.doc_entity_tree.QueryNode;
import query_understanding.query_understand.tree_progress.EntityQueryProgress;
import semantic_matching.common.schema.MatchedEntites;
import semantic_matching.common.schema.MatchedEntityTree;

import java.util.*;

/**
 * Created by julianzliu on 4/25/2017.
 */
public class EntityQueryQueue {

    /*
    TODO：
    避免在查询树生成阶段对 in out 的读写， 造成预先赋值。。。

     */

    static final Logger  logger = LoggerFactory.getLogger(EntityQueryQueue.class);

    /*
    用于确定 边 求解的优先顺序
     */
    private PriorityQueue<QueryEdge.BaseEdge> solvableEdgeQueue;


    /*
    用于保存  可解  的节点，
    可解 -> 计算集合交集
     */
    private Stack<QueryNode.BaseNode> solvableNodeSet;


    private EntityQueryTree tree;
    private MatchedEntityTree matchedEntityTree;
    Map<QueryEdge.BaseEdge, MatchedEntites> edgesEntities ;
    Map<QueryNode.BaseNode, MatchedEntites> nodesEntities ;




    public EntityQueryQueue(EntityQueryTree tree, MatchedEntityTree matchedEntityTree){
        this.solvableEdgeQueue = genEmptyPriorityQueue();
        this.solvableNodeSet = new Stack<>();
        this.tree = tree;
        this.matchedEntityTree = matchedEntityTree;
        this.edgesEntities = matchedEntityTree.edgesEntities;
        this.nodesEntities = matchedEntityTree.nodesEntities;

        initPriorityQueue(this.tree);

    }

    public Integer getSolvableEdgeSize(){
        return this.solvableEdgeQueue.size();
    }

    public Integer getSolvableNodeSize(){
        return this.solvableNodeSet.size();
    }

    public QueryEdge.BaseEdge selectEdge(){
        return this.solvableEdgeQueue.peek();
    }


    public void solvedEdge(QueryEdge.BaseEdge edge){
        /*
        从队列中移除一条边，
        并且将依赖该边的 节点 设置为可解状态
         */
        this.solvableEdgeQueue.remove( edge );
        edge.solved = true;
        // check out node  (O)------>
        checkOutNode( edge.out );
    }

    private void checkOutNode(QueryNode.BaseNode outNode){
        Boolean allSolved = true;
        for(QueryEdge.BaseEdge edge : outNode.outE ){
            if(edge.solved == false){
                allSolved = false;
                break;
            }
        }
        if(allSolved){
            outNode.solveable = true;
            this.solvableNodeSet.push( outNode );
        }
    }

    public QueryNode.BaseNode selectNode(){
        return this.solvableNodeSet.peek();
    }

    public void solvedNode(QueryNode.BaseNode node){
        this.solvableNodeSet.remove( node );
        node.solved = true;
        if(node.inE != null){
            // 将依赖该节点的边设置为可解状态，填充队列
            logger.info("[mark in Edge] {}", node.inE.hashCode() );
            node.inE.solveable = true;
            this.solvableEdgeQueue.add( node.inE );
        }
    }



    private void initPriorityQueue(EntityQueryTree tree){
        // 1) 生成 入边 、出边等  标记
        genDirectedTree( tree );
        //printDirectedTree( tree );


        // 2) 填充队列
        genSolvableEdges( tree );

    }





    /*****************************************************************
     *
     * @param tree
     */
    private void genSolvableEdges( EntityQueryTree tree ){
        QueryNode.BaseNode root = tree.answerEntity;
        if(tree.answerEntity == null){
            logger.error("[genSolvableEdges] Answer Node is None!!!\n");
        }
        dfsNode(root);
    }

    private void dfsNode(QueryNode.BaseNode node){
        logger.info("[dfsNode] node = {}, inE = {}, outE = {}", node.hashCode(), node.inE, node.outE);
        for(QueryEdge.BaseEdge edge : node.outE ) {
            dfsEdge( edge );
        }
    }

    private void dfsEdge(QueryEdge.BaseEdge edge){
        logger.info("[dfsEdge] edge = {}, in = {}, out = {}", edge.hashCode(), edge.in, edge.out );
        if( edge.in.solved ) {
            edge.solveable = true;
            this.solvableEdgeQueue.add( edge );
        }
        else {
            dfsNode(edge.in);
        }
    }










    /*******************************************************************
     *
     * @param tree
     */
    private void genDirectedTree( EntityQueryTree tree ){

        logger.info("\n\n[genDirectedTree] gen Directed Tree .........");

        QueryNode.BaseNode root = tree.answerEntity;

        Set<QueryNode.BaseNode> visitedNodes = new HashSet<>();
        Set<QueryEdge.BaseEdge> visitedEdges = new HashSet<>();

        String indent = "  ";

        recurrentNode( root, indent, visitedNodes, visitedEdges);
    }


    private  void recurrentNode(QueryNode.BaseNode root, String indent, Set<QueryNode.BaseNode> visitedNodes, Set<QueryEdge.BaseEdge> visitedEdges){
        String printStr = "[" + root.nodeType.toString() + "]";
        if(root.nodeType == QueryNode.NodeType.ENTITY ){
            root.solved = true;
            MatchedEntites matchedEntites = new MatchedEntites();
            matchedEntites.entities.add( ((QueryNode.EntityNode)(root)).yago_id );
            this.nodesEntities.put(root, matchedEntites);
            printStr += " " + ((QueryNode.EntityNode)root).yago_id;
        }
        if( root.nodeType == QueryNode.NodeType.CONCEPT){
            root.solved = true;
            printStr += " " + ((QueryNode.ConceptNode)root).yago_id;
        }

        System.out.println(indent + printStr + " " + root + "\nBefore: inE:" + root.inE + " outE:" + root.outE );

        visitedNodes.add( root );

        logger.info("[visitedEdges] ---------------- ");
        for(QueryEdge.BaseEdge edge: visitedEdges)
            logger.info("[edge] {}", edge.hashCode());


        for(QueryEdge.BaseEdge edge: root.edgesSet ){
            if(visitedEdges.contains( edge )){
                // 说明这条边是入边
                root.inE = edge;
            }
            else{
                logger.warn("\n\n[recurrentNode] will add a outE {}\n\n", edge );
                root.outE.add( edge );
                recurrentEdge( edge, indent + "  ", visitedEdges, visitedNodes);
            }
        }

        System.out.println(indent + printStr + " " + root + "\nAfter: inE:" + root.inE + " outE:" + root.outE );
    }

    private  void recurrentEdge(QueryEdge.BaseEdge edge, String indent, Set<QueryEdge.BaseEdge> visitedEdges, Set<QueryNode.BaseNode> visitedNodes){
        String printStr = "<" + edge.edgeType.toString() + ">";
        if(edge.edgeType == QueryEdge.EdgeType.YAGO_EDGE )
            printStr += " " + ((QueryEdge.YagoEdge)edge).yago_label;
        if(edge.edgeType == QueryEdge.EdgeType.OCCUR_EDGE)
            printStr += " " + ((QueryEdge.OccurEdge)edge).text;

        System.out.println( indent + printStr + " " + edge + "\nBefore: out:" + edge.out + " in:" + edge.in );

        visitedEdges.add( edge );

        logger.info("[visitedNodes] ---------------- ");
        for(QueryNode.BaseNode node: visitedNodes)
            logger.info("[node] {}", node.hashCode());

        for(QueryNode.BaseNode root: edge.nodesSet ){
            if(visitedNodes.contains(root)){
                edge.out = root;
            }
            else {
                edge.in = root;
                recurrentNode(root, indent + " ", visitedNodes, visitedEdges);
            }
        }

        System.out.println( indent + printStr + " " + edge + "\nAfter: out:" + edge.out + " in:" + edge.in );
    }












    /***************************************************************
     *
     * @param tree
     */
    private void printDirectedTree( EntityQueryTree tree ){

        logger.info("\n\n[printDirectedTree] printing Directed Tree .........");

        QueryNode.BaseNode root = tree.answerEntity;

        logger.info("[printDirectedTree] root = {}", root.hashCode());

        Set<QueryNode.BaseNode> visitedNodes = new HashSet<>();
        Set<QueryEdge.BaseEdge> visitedEdges = new HashSet<>();

        String indent = "  ";

        recurrentPrintNode( root, indent, visitedNodes, visitedEdges);
    }


    private  void recurrentPrintNode(QueryNode.BaseNode root, String indent, Set<QueryNode.BaseNode> visitedNodes, Set<QueryEdge.BaseEdge> visitedEdges){


        String printStr = "[" + root.nodeType.toString() + "]";
        if(root.nodeType == QueryNode.NodeType.ENTITY ){
            printStr += " " + ((QueryNode.EntityNode)root).yago_id;
        }
        if( root.nodeType == QueryNode.NodeType.CONCEPT){
            printStr += " " + ((QueryNode.ConceptNode)root).yago_id;
        }
        logger.info(indent + printStr + " " + root + " inE:" + root.inE + " outE:" + root.outE );

        if(visitedNodes.contains(root)){
            logger.error("[Circle] node = {}", root );
            return;
        }
        visitedNodes.add( root );

        for(QueryEdge.BaseEdge edge: root.outE ){
            recurrentPrintEdge( edge, indent + "  ", visitedEdges, visitedNodes);
        }
    }

    private  void recurrentPrintEdge(QueryEdge.BaseEdge edge, String indent, Set<QueryEdge.BaseEdge> visitedEdges, Set<QueryNode.BaseNode> visitedNodes){

        String printStr = "<" + edge.edgeType.toString() + ">";
        if(edge.edgeType == QueryEdge.EdgeType.YAGO_EDGE )
            printStr += " " + ((QueryEdge.YagoEdge)edge).yago_label;
        if(edge.edgeType == QueryEdge.EdgeType.OCCUR_EDGE)
            printStr += " " + ((QueryEdge.OccurEdge)edge).text;

        logger.info( indent + printStr + " " + edge + " out:" + edge.out + " in:" + edge.in );

        if(visitedEdges.contains(edge)){
            logger.error("[Circle] edge = {}", edge);
            return;
        }
        visitedEdges.add( edge );

        recurrentPrintNode(edge.in, indent + " ", visitedNodes, visitedEdges);
    }





















    public static PriorityQueue<QueryEdge.BaseEdge> genEmptyPriorityQueue(){
        Comparator<QueryEdge.BaseEdge> querySorter = new QueryPrioritySorter();
        PriorityQueue<QueryEdge.BaseEdge> querySequence = new PriorityQueue<>(querySorter);
        return querySequence;
    }

    public static class QueryPrioritySorter implements Comparator<QueryEdge.BaseEdge> {
        @Override
        public int compare(QueryEdge.BaseEdge x, QueryEdge.BaseEdge y){

            return 0;
        }
    }



}
